#!/usr/bin/env python
#
# generate-proposal.py
#
# A helper script to generate a proposal file with the specified format,
# which is required for the package proposal in the CIP PDP.
#
# Usage:
#   $ ./generate-proposal.py
#
# Finally, this script generates proposal.yml which includes
# the required information for the package proposal.
#
# Copyright (c) 2019 TOSHIBA Corporation
#
# SPDX-License-Identifier: Apache-2.0
#

import common
import datetime
import get_pkg_depends as gpd
import count_cve
import check_deps
import sys

Q1 = "Enter proposer name: "
Q2 = "Choose the Debian version: "
Q3 = "Enter the source package name: "
Q4 = "Choose the required binary packages: "
Q5 = "Choose the dependency package in the 'or' list: "
Q6 = "Choose one of the virtual package provider: "
Q7 = "Are any of the binary packages used in target rootfs?"
Q8 = "Is the source package is categorized in to the security criteria?"
Q9 = "Enter the number of CVEs found so far: "
Q10 = "Provide the reason for proposing this package: "
Q11 = "Do you want to add another source package? "
PROPOSAL_FILE = "proposal.yml"


def die(text):
    print(common.ERROR_TAG + text)
    exit(1)


def is_dep_parent_pkg_in_rootfs(dep_pkg_dict, pr_request_info):
    """
    Verify if the given dependency source package parent package is selected as in_rootfs
    :param dep_pkg_dict:
    :param pr_request_info:
    :return:
    """
    for dep_bin_pkg, dep_par_pkg_info_list in dep_pkg_dict.items():
        for dep_par_pkg_info in dep_par_pkg_info_list:
            if dep_par_pkg_info[1] in pr_request_info.proposed_src_pkgs:
                if pr_request_info.proposed_src_pkgs[dep_par_pkg_info[1]].in_target:
                    return True
    return False


def prepare_src_pkg_info(apt, cve, deb_src_pkg_name, bin_pkg_list, in_target='', reason='', prop_src_pkg_info={}):

    if prop_src_pkg_info:
        src_pkg_info = prop_src_pkg_info
        deb_bin_pkg_dict = prop_src_pkg_info.bin_pkg_dict
        reason = prop_src_pkg_info.reason + '\n' + reason
    else:
        src_pkg_info = common.PDPProposal.SrcPkgInfo()
        deb_bin_pkg_dict = dict()

    for pkg in bin_pkg_list:
        print("\n" + pkg)
        # Ask user to choose if any alternative dependency package ('or' and virtual packages)
        dp_list_final = gpd.get_pkg_depends(pkg, apt)
        if not len(dp_list_final):
            print("\tNo Dependencies in package")
        else:
            for dp in dp_list_final:
                print("\t-" + dp)
        deb_bin_pkg_dict[pkg] = dp_list_final

    # Ask user choose if the package is required to be in target root-fs
    if in_target not in ['True', 'False']:
        in_target = common.input_choose_radio2(Q7, ['True', 'False'])
    if in_target == "False":
        security_criteria = "-"
        n_cve = "-"
    else:
        security_criteria = common.evaluate_security_criteria(apt, deb_bin_pkg_dict.keys())
        # Get the number of CVE's available for the source package
        n_cve = str(cve.count_cve(deb_src_pkg_name))

    # Ask user to input the reason for adding this package
    if reason == '':
        reason = common.input_multiline_text(Q10)

    # Prepare source package information and return
    src_pkg_info.bin_pkg_dict = deb_bin_pkg_dict
    src_pkg_info.in_target = in_target
    src_pkg_info.security_criteria = security_criteria
    src_pkg_info.n_cve = n_cve
    src_pkg_info.reason = reason
    return src_pkg_info


def generate_proposal(pr_request_info):
    """
    Generate the proposal file by taking several input from the user
    :param pr_request_info: object of common.PDPProposal.ProposalInfo
    :return:
    """
    if pr_request_info is None:
        return

    apt = common.Apt()
    cve = count_cve.Cve()
    cve.load_cve_data()
    try:
        # Input proposer name
        if pr_request_info.proposer_name == '':
            pr_request_info.proposer_name = common.input_text(Q1)
        else:
            print("Proposer name: " + pr_request_info.proposer_name)

        # Take proposal date as current date
        pr_request_info.proposal_date = str(datetime.date.today().strftime("%Y/%m/%d"))
        print("Proposal date: " + pr_request_info.proposal_date)

        # Input debian version
        if pr_request_info.proposed_debian_version == '':
            pr_request_info.proposed_debian_version = common.input_choose_radio(Q2, common.DEBIAN_CODE_NAMES, 8)
        print("Proposed Debian version: " + pr_request_info.proposed_debian_version)

        if not apt.apt_initialize(pr_request_info.proposed_debian_version):
            del apt
            die("Apt initialize is failed")

        pdp_info = common.PDPInfo(pr_request_info.proposed_debian_version)
        pdp_info.load_pdp()
        gpd.load_prv_sel_pkg_list(pdp_info)

        # Loop until user want to propose multiple source packages
        while True:
            # Input source package name
            deb_src_pkg_name = ""
            # Loop until a valid source package is entered by user
            while True:
                src_pkg_name_input = common.input_text(Q3)
                src_pkg_name, src_pkg_ver, src_pkg_bin_list = apt.apt_cache_get_src_info(src_pkg_name_input)
                if src_pkg_name != src_pkg_name_input:
                    print("Not valid source package!!!")
                    continue
                else:
                    deb_src_pkg_name = src_pkg_name_input
                    break

            # if no binary packages found for the source package name, warn user and continue
            if len(src_pkg_bin_list) == 0:
                print("Warning: There are no binary packages found to this source package name")
                continue

            # Ask user to choose binary packages
            if pdp_info.is_src_pkg_exist(deb_src_pkg_name):
                bin_pkg_list = common.input_choose_bin_pkg(src_pkg_bin_list, pdp_info.get_src_pkg_info(
                    deb_src_pkg_name).bin_pkg_data_dict.keys())
            else:
                bin_pkg_list = common.input_choose_bin_pkg(src_pkg_bin_list)

            # Prepare source package information with user provided details
            pr_request_info.proposed_src_pkgs[deb_src_pkg_name] = prepare_src_pkg_info(apt, cve, deb_src_pkg_name, bin_pkg_list)

            print("\n")
            # Ask user whether to add another package
            if common.input_choose_radio2(Q11, ['Yes', 'No']) == "No":
                break

        # Check unmet dependencies and Loop until all unmet dependencies are satisfied
        while True:
            unmet_dep_dict = check_deps.get_unmet_dep(apt, pdp_info, pr_request_info)
            if unmet_dep_dict:
                check_deps.display_unmet_dep(unmet_dep_dict)
                # if any unmet dependencies, Ask user whether to add the dependencies to the proposal
                if common.input_choose_radio2("There are unmet dependencies found, do you want to add them to proposal?",
                                             ['Yes', 'No']) == "Yes":
                    for dep_src_pkg, dep_pkg_info in unmet_dep_dict.items():
                        print("\nSource package name: " + dep_src_pkg)
                        print("Binary packages:")

                        # if any of the parent dependency package is selected in target root-fs, then its dependency
                        # packages should also be part of root-fs, auto fill 'in_target' based on the above condition
                        in_target = str(is_dep_parent_pkg_in_rootfs(dep_pkg_info, pr_request_info))

                        # 'reason' for the unmet dependency packages is auto filled with its depended by packages list
                        # 'dep_bin_pkg_name is a dependency package for bin_pkg_name in src_pkg_name'
                        depend_by_list = list()
                        for dep_name, dep_info in dep_pkg_info.items():
                            depend_by_list.append(dep_name + " is a dependency for " + ", ".join(
                                [info[0] + " (in " + info[1] + ")" for info in dep_info]))
                        reason = "\n".join(depend_by_list)

                        # Prepare source package information with user provided details
                        pr_request_info.proposed_src_pkgs[dep_src_pkg] = prepare_src_pkg_info(apt, cve, dep_src_pkg,
                                                                                              dep_pkg_info.keys(),
                                                                                              in_target,
                                                                                              reason,
                                                                                              pr_request_info.proposed_src_pkgs.get(
                                                                                                  dep_src_pkg))
                else:
                    break
            else:
                break
    finally:
        del apt

    print("\n")
    print("################################################")
    print("# Final proposal request")
    print("################################################")
    common.PDPProposal().print_req_info(pr_request_info)
    print("################################################")
    common.PDPProposal().save(pr_request_info, PROPOSAL_FILE)
    print("Proposal Saved in file: " + PROPOSAL_FILE)


def usage():
    print("Usage: ./generate-proposal [-a <existing-proposal.yml>]")
    exit(1)


if __name__ == "__main__":
    if len(sys.argv) > 2:
        if '-a' in sys.argv[1:]:
            prop_info = common.PDPProposal().load(sys.argv[2])
            print("# Existing proposal information:")
            common.PDPProposal().print_req_info(prop_info)
            print("# Additional proposal data:")
            generate_proposal(prop_info)
        else:
            usage()
    else:
        generate_proposal(common.PDPProposal.ProposalInfo())
